package net.sf.nlp.ltag.util;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Stack;

import org.apache.log4j.Logger;

import net.sf.nlp.ltag.data.LTAGNode;
import net.sf.nlp.ltag.data.Word;
import net.sf.nlp.ltag.extract.LTAGRelation;

public class LTAG 
{
	public static final String POS_TAGSET = "POS_TAGSET";
	public static final String POS_DELETE = "POS_DELETE";
	public static final String POS_PU_SUPPORT = "POS_PU_SUPPORT";
	public static final String SEPERATOR = ",";
	public static final String PHRASE = "PHRASE_";
	
	protected static Logger logger =  Logger.getLogger(LTAG.class);
	
	public static Properties relationProperties = new Properties();
	public static Properties postagSetProperties = new Properties();
	public static Properties ltagProperties = new Properties();
	public static Map<String, String> phrasesMap = new HashMap<String, String>();
	
	static
	{
		loadPosProperties();
		loadRelationProperties();
		loadLtagProperties();
		loadPhrasesMap();
	}
	
	private static void loadPosProperties()
	{
		try 
		{
			InputStream is = LTAGRelation.class.getResourceAsStream("/parser/ltag_pos.properties");
			postagSetProperties.load(is);
			is.close();
		} 
		catch (IOException e) 
		{
			logger.error(e, e);
		}
	}
	
	private static void loadLtagProperties()
	{
		try 
		{
			InputStream is = LTAGRelation.class.getResourceAsStream("/parser/ltag.properties");
			ltagProperties.load(is);
			is.close();
		} 
		catch (IOException e) 
		{
			logger.error(e, e);
		}
	}
	
	public static void saveLtagProperties()
	{
		try 
		{
			File file = new File(LTAGRelation.class.getResource("/parser/").getPath() + "ltag.properties");
			
			OutputStream os = new FileOutputStream(file);
			ltagProperties.store(os, null);
		}
		catch (IOException e) 
		{
			logger.error(e, e);
		}
	}
	
	private static void loadPhrasesMap()
	{
		String[] phrases = LTAG.relationProperties.getProperty(PHRASE).split(SEPERATOR);
		
		for (String phrase : phrases)
		{
			phrase = phrase.trim();
			if (phrase.length() == 0)
				continue;
			
			phrasesMap.put(phrase, LTAG.relationProperties.getProperty(PHRASE + phrase));
		}
	}
	
	private static void loadRelationProperties()
	{
		try 
		{
			InputStream is = LTAGRelation.class.getResourceAsStream("/parser/ltag_relation.properties");
			relationProperties.load(is);
			is.close();
		} 
		catch (IOException e) 
		{
			logger.error(e, e);
		}
	}
	
	public static LTAGNode buildLTAGNodeFromText(String text)
	{
		return buildLTAGNodeFromText(text, false);
	}
	
	public static LTAGNode buildLTAGNodeFromText(String text, boolean isBracketTree)
	{
		try
		{
			Stack<Object> stack = new Stack<Object>();
			StringBuffer sb = new StringBuffer();
			
			for (int i = 0; i < text.length(); i++)
			{
				char ch = text.charAt(i);
				
				if (ch == '(')
				{
					push2Stack(stack, sb);
					stack.push(String.valueOf(ch));
				}
				else if (ch == ' ' || ch == '\r' || ch == '\n' || ch == '\t')
				{
					push2Stack(stack, sb);
				}
				else if (ch == ')')
				{
					push2Stack(stack, sb);
					popFromStack(stack, isBracketTree, false);
				}
				else
				{
					sb.append(String.valueOf(ch));
				}
			}
			
			if (stack.size() > 1)
				popFromStack(stack, isBracketTree, true);
			
			LTAGNode node = null;
			
			if (stack.size() > 0)
				node = (LTAGNode)stack.pop();
			
			return node;
		}
		catch (Exception ex)
		{
			logger.error(text);
			logger.error(ex, ex);
		}
		
		return null;
	}
	
	private static void push2Stack(Stack<Object> stack, StringBuffer sb)
	{
		if (sb.length() > 0)
		{
			stack.push(sb.toString());
			sb.delete(0, sb.length());
		}
	}
	/*
	private static void popFromStack(Stack<Object> stack, boolean isBracketTree)
	{
		Object item;
		List<LTAGNode> children = new ArrayList<LTAGNode>();
		LTAGNode parentNode = null;
		
		while (true)
		{
			item = stack.pop();
			
			if (item instanceof LTAGNode)
				children.add(0, (LTAGNode)item);
			else if (item instanceof String)
			{
				if ("(".equals(item))
				{
					if (stack.isEmpty())
						stack.addAll(children);
					else
					{
						item = stack.pop();
						if (item instanceof LTAGNode)
						{
							parentNode = (LTAGNode)item;
							
							if (parentNode.getWord() != null)
							{
								children.add(0, parentNode);
								parentNode = null;
							}
						}
						else if ("(".equals(item))
						{
							stack.push("(");
							//tryRecognizeLexical(isBracketTree, children);
						}
						else
							parentNode = buildLTAGNode((String)item);
						
						if (parentNode != null)
						{
							if (parentNode.getChildren() == null)
								parentNode.setChildren(new ArrayList<LTAGNode>());
							
							parentNode.getChildren().addAll(children);
							
							for (LTAGNode child : children)
								child.setParent(parentNode);
							
							stack.push(parentNode);
						}
						else
							stack.addAll(children);
					}
					
					break;
				}
				else
				{
					children.add(0, buildLTAGNode((String)item));
					tryRecognizeLexical(isBracketTree, children);
				}
			}
		}
	}*/
	
	private static void popFromStack(Stack<Object> stack, boolean isBracketTree, boolean lastStep) throws Exception
	{
		Object item;
		List<LTAGNode> popedChildren = new ArrayList<LTAGNode>();
		List<LTAGNode> children = new ArrayList<LTAGNode>();
		
		while (true)
		{
			item = stack.pop();
			
			if (item instanceof LTAGNode)
				popedChildren.add(0, (LTAGNode)item);
			else if (item instanceof String)
			{
				if ("(".equals(item))
				{
					if (lastStep)
						throw new Exception("The brackets are not matched, please check the tree structure");
					
					stack.addAll(popedChildren);
					stack.addAll(children);
					break;
				}
				
				LTAGNode node = buildLTAGNode((String)item);
				
				if (popedChildren.size() > 0)
				{
					node.setChildren(new ArrayList<LTAGNode>());
					node.getChildren().addAll(popedChildren);
					
					for (LTAGNode popedChild : popedChildren)
						popedChild.setParent(node);
					
					popedChildren.clear();
					children.add(0, node);
				}
				else
					children.add(0, node);
				
				tryRecognizeLexical(isBracketTree, children);
				
				if (stack.size() == 0)
				{
					stack.addAll(children);
					break;
				}
			}
			
			
		}
	}
	
	private static void tryRecognizeLexical(boolean isBracketTree, List<LTAGNode> children)
	{
		if (!isBracketTree || children.size() != 2)
			return;
		
		LTAGNode firstChild = children.get(0);
		if (firstChild.getWord() != null)
			return;
		
		LTAGNode secondChild = children.get(1);
		if ("$".equals(secondChild.getType()) || "*".equals(secondChild.getType()))
			return;
		
		String posString = postagSetProperties.getProperty(POS_TAGSET);
		
		if (secondChild.getNodeText().startsWith("*") || posString.contains(firstChild.getNodeText() + SEPERATOR))
		{
			Word word = new Word();
			word.setLexical(children.get(1).getNodeText());
			firstChild.setWord(word);
			children.remove(1);
		}
	}
	
	private static LTAGNode buildLTAGNode(String nodeText)
	{
		LTAGNode node = new LTAGNode();
		
		if (nodeText.endsWith("#") || nodeText.endsWith("$") || (nodeText.endsWith("*") && nodeText.indexOf("*") == nodeText.lastIndexOf("*")))
		{
			node.setType(nodeText.substring(nodeText.length() - 1, nodeText.length()));
			nodeText = nodeText.substring(0, nodeText.length() - 1);
		}
		
		
		int index = nodeText.indexOf("-");
		String semanticTag = null;
		
		if (index > 0)
		{
			semanticTag = nodeText.substring(index + 1);
			nodeText = nodeText.substring(0, index);
		}
		
		node.setNodeText(nodeText);
		node.setSemanticTag(semanticTag);
		
		return node;
	}

}
